import pandas as pd

df = pd.read_csv('data/winequality-red.csv')
print(df)

# 映射字典
quality_mapping = {
    3: 0,
    4: 1,
    5: 2,
    6: 3,
    7: 4,
    8: 5
}

# 可以使用 pandas 的 map 函数，结合任何字典，将给定列中的值转换为字典中的对应值。
df.loc[:, "quality"] = df.quality.map(quality_mapping)
print(df)

# 过拟合
# 了解什么是过拟合，先切割数据集

'''
下面这行代码的作用是对 DataFrame df 进行随机重排，即打乱 DataFrame 中的行顺序，并重新设置索引为连续的整数索引。

df.sample(frac=1) 会对 DataFrame 进行随机重排 参数 frac=1 表示采样比例为 1，即采样全部行。
reset_index(drop=True)：重设索引，drop=True 表示不保留原来的索引列，而是直接丢弃。这样就会生成一个新的索引列，从 0 开始递增，对应新的 DataFrame 的行顺序。
'''
df = df.sample(frac=1).reset_index(drop=True)

df_train = df.head(1000)
df_test = df.tail(1000)
print(df_train)
print(df_test)

# 在训练集上训练一个决策树模型。对于决策树模型，我将使用 scikit-learn
from sklearn import tree
from sklearn import metrics

'''
使用 scikit-learn 库中的 DecisionTreeClassifier 类创建了一个决策树分类器对象。
参数 max_depth=3 指定了决策树的最大深度为 3，即决策树在构建过程中最多可以有 3 层节点。这有助于控制决策树的复杂度，防止过拟合。
'''
clf = tree.DecisionTreeClassifier(max_depth=3)

# 选择你想要用于训练的列，这些列将作为模型的特征。
cols = ['fixed acidity',
        'volatile acidity',
        'citric acid',
        'residual sugar',
        'chlorides',
        'free sulfur dioxide',
        'total sulfur dioxide',
        'density',
        'pH',
        'sulphates',
        'alcohol']


# 测试样本数据量要和测试样本数据量相同
clf.fit(df_train[cols], df_test['quality'])

'''
计算准确率
使用训练好的分类器 clf 对训练集中的特征数据进行预测

clf.predict 是一个用于对新数据进行预测的方法，通常在机器学习中使用。
它接受一个数据集作为输入，并返回一个包含预测结果的数组或列表

'''
train_predictions = clf.predict(df_train[cols])
test_predictions = clf.predict(df_test[cols])

'''
metrics.accuracy_score 是一个用于计算分类模型准确率的函数，通常用于评估分类器的性能。
在使用时，需要将真实的类别标签和模型预测的类别标签作为参数传递给该函数，然后它会返回一个表示分类器准确率的值。

metrics：这是 Scikit-learn 库中的一个模块，提供了各种用于评估模型性能的函数和指标。
accuracy_score：这是 metrics 模块中的一个函数，用于计算分类器的准确率。
参数：
    y_true：真实的类别标签，通常是一个列表或数组。
    y_pred：模型预测的类别标签，通常也是一个列表或数组。
返回值：
    准确率：表示分类器正确分类的样本比例，取值范围为 0 到 1，值越接近 1 表示分类器性能越好。
'''

train_accuracy = metrics.accuracy_score(
    df_train['quality'], train_predictions
)
print(train_accuracy)

test_accuracy = metrics.accuracy_score(
    df_test.quality, test_predictions
)
print(test_accuracy)

# 下面计算不同max_depth下的准确率，并制作一张图表
import matplotlib
import matplotlib.pyplot as plt
import seaborn as sns

# 用于设置 x 轴刻度标签的大小为 20
matplotlib.rc('xtick', labelsize=20)
matplotlib.rc('ytick', labelsize=20)

# 初始准确率设为0.5
train_accuracies = [0.5]
test_accuracies = [0.5]

cols = [
        'fixed acidity',
        'volatile acidity',
        'citric acid',
        'residual sugar',
        'chlorides',
        'free sulfur dioxide',
        'total sulfur dioxide',
        'density',
        'pH',
        'sulphates', 'alcohol'
    ]
for depth in range(1, 25):
    # init the model
    clf = tree.DecisionTreeClassifier(max_depth=depth)

    # fit the model on given features
    clf.fit(df_train[cols], df_train.quality)
    # create training & test predictions
    train_predictions = clf.predict(df_train[cols])
    test_predictions = clf.predict(df_test[cols])
    # calculate training & test accuracies
    train_accuracy = metrics.accuracy_score(
        df_train.quality, train_predictions
    )
    test_accuracy = metrics.accuracy_score(
        df_test.quality, test_predictions
    )

    # append accuracies
    train_accuracies.append(train_accuracy)
    test_accuracies.append(test_accuracy)

# 生成图表
plt.figure(figsize=(10, 5))
sns.set_style("whitegrid")
plt.plot(train_accuracies, label="train accuracy")
plt.plot(test_accuracies, label="test accuracy")
plt.legend(loc="upper left", prop={'size': 15})
plt.xticks(range(0, 26, 5))
plt.xlabel("max_depth", size=20)
plt.ylabel("accuracy", size=20)
plt.show()

'''
Occam’s razor
Occam's razor（奥卡姆剃刀）是一种科学原则，通常用于指导理论的选择和解释。
该原则最早由英国哲学家威廉·奥卡姆（William of Ockham）提出，因此得名。

奥卡姆剃刀的核心思想是：在面对多个理论或解释时，应该优先选择最简单的那个。
简单的理论更具有解释力和预测能力，因为它们不依赖于过多的假设或复杂的构建。
换句话说，如果有多个理论能够解释观察到的现象，那么应该选择最简单、最直接的那个理论。

在科学研究中，奥卡姆剃刀被视为一种有效的启发式原则，可以帮助科学家避免不必要的复杂性，
并推动科学理论的发展。
然而，需要注意的是，奥卡姆剃刀并不意味着简单就一定是正确的，而是要在简单和解释力之间取得平衡，
确保理论的简洁性和有效性。

将奥卡姆剃刀运用到解决过拟合的问题
'''

'''
hold-out set
Hold-out set 是机器学习中常用的一种数据集分割方法，用于评估模型的性能和泛化能力。
该方法将原始数据集划分为训练集（training set）和测试集（test set），其中训练集用于训练模型，而测试集则用于评估模型的性能。
是将原始数据集按照一定比例

Hold-out set 方法的优点是简单直观，易于理解和实现。
然而，它也存在一些缺点，例如对划分比例和随机性敏感，可能会导致评估结果的不稳定性。
因此，在实际应用中，还需要结合交叉验证等方法来更全面地评估模型的性能。
'''

'''
k-fold cross-validation
K-fold 交叉验证是一种常用的模型评估方法，
它将原始数据集分成 k 个相似大小的子集，每个子集称为一个 fold。
然后，每次选择其中一个 fold 作为测试集，剩余的 k-1 个 fold 组成训练集，进行模型的训练和评估。
这个过程重复 k 次，每个 fold 都会被用作一次测试集，从而获得 k 个模型性能评估结果。

'''
from sklearn import model_selection

df = df_train
# 我们创建一个名为 kfold 的新列，并用 -1 填充它。
df["kfold"] = -1
df = df.sample(frac=1).reset_index(drop=True)
'''
这段代码创建了一个 KFold 对象，用于进行 K 折交叉验证。
    KFold：这是一个交叉验证方法，它将数据集分成 K 个不同的子集，称为 folds。
    n_splits=5：这个参数指定了将数据集分成几个 folds，这里设置为 5，表示将数据集分成 5 个子集进行交叉验证。
'''
kf = model_selection.KFold(n_splits=5)

for fold, (trn_, val_) in enumerate(kf.split(X=df)):
    df.loc[val_, 'kfold'] = fold
# 保存数据
df.to_csv("data/train_folds.csv", index=False)

'''
stratified k-fold

Stratified k-fold 交叉验证是 k-fold 交叉验证的一种变体，它在分割数据集时保持了类别分布的一致性。
具体来说，Stratified k-fold 确保每个 fold 中的类别比例与整个数据集中的类别比例相似。

在机器学习任务中，特别是在分类问题中，类别不平衡可能会导致模型评估的偏差。
Stratified k-fold 可以帮助解决这个问题，因为它确保每个 fold 中都包含代表性的类别样本。

使用 Stratified k-fold 时，数据集中的每个类别都应该能够在每个 fold 中得到相似的比例。
这样可以确保在每次交叉验证中，模型都能够看到足够数量的每个类别的样本，从而更好地评估模型在不同类别上的性能。
'''

df = df_train
df["kfold"] = -1
df = df.sample(frac=1).reset_index(drop=True)
# 获取target
y = df.target.values
'''
这段代码使用了 Scikit-learn 中的 StratifiedKFold 类，用于执行 Stratified k-fold 交叉验证。具体来说：
model_selection 模块是 Scikit-learn 中用于模型选择和评估的一部分。
StratifiedKFold 类是用于执行 Stratified k-fold 交叉验证的类。
在这里，n_splits=5 意味着数据集将被分成 5 个 folds。
每个 fold 中的样本数量大致相等，并且每个 fold 中的类别比例与整个数据集中的类别比例相似。
'''
kf = model_selection.StratifiedKFold(n_splits=5)
for f, (t_, v_) in enumerate(kf.split(X=df, y=y)):
    df.loc[v_, 'kfold'] = f
df.to_csv("train_stratified_folds.csv", index=False)



'''
Leave-one-out交叉验证（LOOCV）
Leave-one-out交叉验证（LOOCV）是一种特殊的交叉验证方法，适用于较小的数据集。在LOOCV中，每次迭代中只保留一个样本作为验证集，而其余样本用于训练。这意味着对于包含n个样本的数据集，会执行n次训练和验证，每次都使用一个样本作为验证集。最后，通过对这些迭代的结果进行平均来评估模型的性能。
LOOCV的优点是每个样本都有机会作为验证集，因此能够提供对模型性能的准确评估。然而，由于需要执行多次训练，对于大型数据集来说计算成本较高。
总的来说，LOOCV在数据集较小且计算资源允许的情况下是一种有效的交叉验证方法。
'''
df = df_train
df["kfold"] = -1
df = df.sample(frac=1).reset_index(drop=True)
# 获取target
y = df.target.values
kf = model_selection.LeaveOneOut(n_splits=5)
for f, (t_, v_) in enumerate(kf.split(X=df, y=y)):
    df.loc[v_, 'kfold'] = f
df.to_csv("train_stratified_folds.csv", index=False)

'''
Group k-fold cross-validation
scikit-learn offers a type of cross-validation known as GroupKFold. Here the patients can be considered as groups.
分成组
Group k-fold 交叉验证是 k-fold 交叉验证的一种变体，在将数据分割为训练集和测试集时考虑了数据的分组结构。
在传统的 k-fold 交叉验证中，数据被随机分成 k 个互斥的子集，其中一个子集作为测试集，其余子集用作训练集。
然而，在 Group k-fold 交叉验证中，将这些子集分配给测试集和训练集时考虑了数据中存在的分组情况。

这种交叉验证方法通常用于数据具有自然分组或聚类结构的情况，
例如在医学研究中患者被分组，或者在时间序列数据中。在处理这类数据时，随机地将其分为 k 个子集进行交叉验证可能导致训练集和测试集之间的数据泄漏或信息泄漏。
Group k-fold 交叉验证通过确保在训练集和测试集中保留分组来解决这个问题。
在 Group k-fold 交叉验证中，数据被分成 k 个子集，每个子集都包含来自相同组或聚类的数据点。
这些数据点可能已经泄漏到训练集中。通过在交叉验证过程中保留分组结构，Group k-fold 交叉验证提供了对模型在未见数据上性能的更准确估计。
'''
